use crate::db::entities::knowledge_progress::{self, POINT_MASTERED, POINT_NEED_TO_LEARN_TODAY, POINT_NOT_LEARNED};
use crate::db::entities::scheduler::Model;
use crate::db::entities::task::{self, STATUS_COMPLETED};
use crate::db::{db_knowledge, db_knowledge_progress, db_scheduler, db_task};
use crate::rest_api::api_scheduler::{CreateParams, Dashboard, ImportParams, Scheduler, SearchParams, UpdateParams};
use crate::rest_api::service::srv_knowledge;

use anyhow::Result;
use log::trace;

use rbase::define::{ApiError, BaseError};
use rbase::id_utils::str2id;
use rbase::time_utils::{DAY, get_systemtime_millis};
use rbase_iam::db::define::SYSTEM_ID;
use rbase_iam::rest_api::api_user::UserToken;

pub async fn scheduler_from_model(scheduler_model: &Model) -> Result<Scheduler> {
    let scheduler = Scheduler::from_model(scheduler_model);
    Ok(scheduler)
}

pub async fn find_scheduler_by_id_with_user(user_token: &UserToken, scheduler_id_str: &String) -> Result<Model, ApiError> {
    let scheduler_id = str2id(scheduler_id_str)?;
    let scheduler_model = db_scheduler::find_by_id_and_owner_ids(scheduler_id, &user_token.owner_ids)
        .await?
        .ok_or_else(|| BaseError::ResourceNotFound(scheduler_id_str.to_string()))?;
    return Ok(scheduler_model);
}

pub async fn find_scheduler_by_id(user_token: &UserToken, scheduler_id: &String) -> Result<Scheduler, ApiError> {
    let scheduler_model = find_scheduler_by_id_with_user(user_token, scheduler_id).await?;
    Ok(scheduler_from_model(&scheduler_model).await?)
}

pub async fn delete_scheduler_by_id(user_token: &UserToken, scheduler_id: &String) -> Result<(), ApiError> {
    let scheduler_model = find_scheduler_by_id_with_user(user_token, scheduler_id).await?;
    // 普通用户不能修改系统资源
    if !user_token.is_admin && scheduler_model.owner_id == Some(SYSTEM_ID) {
        return Err(BaseError::PermissionDenied.into());
    }
    db_scheduler::delete_by_id(scheduler_model.id).await?;
    Ok(())
}

pub async fn update_scheduler_by_id(user_token: &UserToken, scheduler_id: &String, new_scheduler: &UpdateParams) -> Result<(), ApiError> {
    let scheduler_model = find_scheduler_by_id_with_user(user_token, scheduler_id).await?;
    // 普通用户不能修改系统资源
    if !user_token.is_admin && scheduler_model.owner_id == Some(SYSTEM_ID) {
        return Err(BaseError::PermissionDenied.into());
    }
    let new_model = Model {
        id: scheduler_model.id,
        name: Some(new_scheduler.name.clone()),
        ..Default::default()
    };
    db_scheduler::update(new_model).await?;
    Ok(())
}

pub async fn create_scheduler(user_token: &UserToken, new_scheduler: &CreateParams) -> Result<Scheduler> {
    let new_model = Model {
        name: Some(new_scheduler.name.clone()),
        owner_id: Some(user_token.id),
        ..Default::default()
    };
    let created_model = db_scheduler::insert(new_model).await?;
    Ok(scheduler_from_model(&created_model).await?)
}

pub async fn search(user_token: &UserToken, scheduler_search_params: &SearchParams) -> Result<(Vec<Scheduler>, i64)> {
    let owner_ids = if user_token.is_admin { None } else { Some(vec![SYSTEM_ID, user_token.id]) };
    let (schedulers, total) = db_scheduler::search(&db_scheduler::SearchParams {
        name: scheduler_search_params.name.clone(),
        offset: scheduler_search_params.offset,
        limit: scheduler_search_params.limit,
        order_by: scheduler_search_params.order_by.clone(),
        order: scheduler_search_params.order.clone(),
        owner_ids,
    })
    .await?;

    let mut scheduler_results = Vec::new();
    for scheduler in schedulers {
        scheduler_results.push(scheduler_from_model(&scheduler).await?);
    }

    Ok((scheduler_results, total))
}

pub async fn import_knowledge_to_scheduler(user_token: &UserToken, scheduler_id: &String, import_params: &ImportParams) -> Result<(), ApiError> {
    let scheduler_model = find_scheduler_by_id_with_user(user_token, scheduler_id).await?;
    match &import_params.ids {
        Some(ids) => {
            for id_str in ids {
                let knowledge = srv_knowledge::fing_knowledge_by_id_with_user(user_token, id_str).await?;
                insert_scheduler_knowledge(user_token, scheduler_model.id, knowledge.id).await?;
            }
        }
        None => {
            let owner_ids = if user_token.is_admin { None } else { Some(vec![SYSTEM_ID, user_token.id]) };
            let (knowledge, total) = db_knowledge::search(&db_knowledge::SearchParams {
                name: import_params.name.clone(),
                subject_id: match &import_params.subject_id {
                    Some(id) => Some(str2id(id)?),
                    None => None,
                },
                category_id: match &import_params.category_id {
                    Some(id) => Some(str2id(id)?),
                    None => None,
                },
                owner_ids,
                ..Default::default()
            })
            .await?;

            trace!("Importing knowledge to scheduler: {}", total);
            for knowledge in knowledge {
                insert_scheduler_knowledge(user_token, scheduler_model.id, knowledge.id).await?;
            }
        }
    }
    Ok(())
}

pub async fn insert_scheduler_knowledge(user_token: &UserToken, scheduler_id: i64, knowledge_id: i64) -> Result<()> {
    match db_knowledge_progress::find_by_scheduler_id_and_knowledge_id(scheduler_id, knowledge_id).await? {
        Some(_) => return Ok(()),
        None => {}
    }
    db_knowledge_progress::insert_scheduler_knowledge(knowledge_progress::Model {
        scheduler_id: Some(scheduler_id),
        knowledge_id: Some(knowledge_id),
        owner_id: Some(user_token.id),
        ..Default::default()
    })
    .await?;
    Ok(())
}

pub async fn get_scheduler_dashboard(user_token: &UserToken, scheduler_id: &String) -> Result<Dashboard> {
    find_scheduler_by_id(user_token, scheduler_id).await?;

    let mut dashboard = Dashboard::default();

    let scheduler_id = str2id(scheduler_id)?;

    refresh(scheduler_id).await?;

    dashboard.knowledge_total = db_knowledge_progress::count_by_scheduler_id(scheduler_id).await?;
    dashboard.knowledge_need_learned_today = db_knowledge_progress::count_by_scheduler_id_and_point(scheduler_id, POINT_NEED_TO_LEARN_TODAY).await?;
    dashboard.knowledge_mastered = db_knowledge_progress::count_by_scheduler_id_and_point(scheduler_id, POINT_MASTERED).await?;
    let learning1 = db_knowledge_progress::count_by_scheduler_id_and_point_between(scheduler_id, POINT_NOT_LEARNED, POINT_MASTERED).await?;
    let learning2 = db_knowledge_progress::count_by_scheduler_id_and_point_between(scheduler_id, POINT_NEED_TO_LEARN_TODAY, POINT_NOT_LEARNED).await?;
    dashboard.knowledge_learning = learning1 + learning2;

    dashboard.task_total = db_task::count_by_scheduler_id(scheduler_id).await?;
    dashboard.task_completed = db_task::count_by_scheduler_id_and_status(scheduler_id, STATUS_COMPLETED).await?;

    Ok(dashboard)
}

pub async fn validate_scheduler_id(user_token: &UserToken, scheduler_id: &Option<String>) -> Result<Option<i64>, ApiError> {
    match scheduler_id {
        Some(scheduler_id) => {
            let scheduler_id_str = str2id(&scheduler_id)?;
            db_scheduler::find_by_id_and_owner_ids(scheduler_id_str, &user_token.owner_ids)
                .await?
                .ok_or_else(|| BaseError::ResourceNotFound(scheduler_id_str.to_string()))?;
            Ok(Some(scheduler_id_str))
        }
        None => Ok(None),
    }
}

pub async fn refresh(scheduler_id: i64) -> Result<()> {
    let (progresses, _total) = db_knowledge_progress::search(&db_knowledge_progress::SearchParams {
        scheduler_id: Some(scheduler_id),
        ..Default::default()
    })
    .await?;
    for progress in progresses {
        let knowledge_id = match progress.knowledge_id {
            Some(id) => id,
            None => {
                continue;
            }
        };
        let tasks = db_task::find_by_scheduler_id_and_knowledge_id(scheduler_id, knowledge_id).await?;
        let count = tasks.len() as i64;
        let point = tasks_to_point(&tasks);
        if progress.learned_count != Some(count) || progress.learning_point != Some(point) {
            db_knowledge_progress::update(knowledge_progress::Model {
                id: progress.id,
                learned_count: Some(count),
                learning_point: Some(point),
                ..Default::default()
            })
            .await?;
        }
    }

    Ok(())
}

pub fn tasks_to_point(tasks: &Vec<task::Model>) -> i64 {
    let count = tasks.len();
    if count <= 0 {
        return knowledge_progress::POINT_NOT_LEARNED;
    }

    let task = &tasks[0];
    static POINT_DAYS: [i64; 9] = [0, 1, 2, 4, 7, 15, 45, 75, 105];
    match count {
        1..7 => {
            let days = POINT_DAYS[count + 1] - POINT_DAYS[count];
            let next_task_time = task.created_at.unwrap_or(0) + days * DAY;
            let now = get_systemtime_millis();
            let t = next_task_time - now;
            if t < DAY {
                return knowledge_progress::POINT_NEED_TO_LEARN_TODAY;
            }
            if t < (DAY * 7) {
                return knowledge_progress::POINT_NEED_TO_LEARN_THIS_WEEK;
            }
            if t < (DAY * 30) {
                return knowledge_progress::POINT_NEED_TO_LEARN_THIS_MONTH;
            }
            return count as i64;
        }
        _ => POINT_MASTERED,
    };
    return knowledge_progress::POINT_MASTERED;
}
